/* Copyright (C) 2010 Luca Piccinelli
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "imgops/filters/GaussBlurOp.h"
#include "common/utility_data_structures.h"
#include "machine_learning/statistical_models/normal_bayes_model.h"

#include <map>
#include <string>
#include <highgui.h>

using namespace cv;
using namespace std;

namespace NAMESPACE{

// ****** Functor implementation ***********************************************
bool GaussBlurFunc::operator()(const cv::Mat& src, cv::Mat& dst,
                               size_t height, size_t width,
                               double sigmaX, double sigmaY,
                               int borderType){
    cv::Size sz(width, height);
    cv::GaussianBlur(src, dst, sz, sigmaX, sigmaY, borderType);
    return true;
}
// -----------------------------------------------------------------------------

// ****** Constants assignment *************************************************
const size_t GaussBlurOp::MIN_INPUT_NUM = 1;
const size_t GaussBlurOp::MIN_CRITERIA_NUM = 3;
const size_t GaussBlurOp::MIN_OUTPUT_NUM = 1;
// -----------------------------------------------------------------------------

// ****** Operation implementation *********************************************
GaussBlurOp::~GaussBlurOp(){}
GaussBlurOp::GaussBlurOp() : AbstractOperation<GaussBlurFunc>(){
    
    min_input_num = MIN_INPUT_NUM;
    min_criteria_num = MIN_CRITERIA_NUM;
    min_output_num = MIN_OUTPUT_NUM;

    k1.set_key(INPUT_NS,  "img"); k1.setType(TypeParseTraits<cv::Mat*>::name());
    k2.set_key(OUTPUT_NS, "img"); k2.setType(TypeParseTraits<cv::Mat*>::name());

    k3.set_key(CRITERIA_NS, "size.width"); k3.setType(TypeParseTraits<int>::name());
    k4.set_key(CRITERIA_NS, "size.height"); k4.setType(TypeParseTraits<int>::name());
    k5.set_key(CRITERIA_NS, "order.derivative.x"); k5.setType(TypeParseTraits<double>::name());
    k6.set_key(CRITERIA_NS, "order.derivative.y"); k6.setType(TypeParseTraits<double>::name());
    k7.set_key(CRITERIA_NS, "constant"); k7.setType(TypeParseTraits<int>::name());

    input_keys->push_back(k1);
    output_keys->push_back(k2);

    criteria_keys->push_back(k3);
    criteria_keys->push_back(k4);
    criteria_keys->push_back(k5);
    criteria_keys->push_back(k6);
    criteria_keys->push_back(k7);
}
GaussBlurOp::GaussBlurOp(const GaussBlurOp& gbo) : AbstractOperation<GaussBlurFunc>(gbo),
                                                   k1(gbo.k1),
                                                   k2(gbo.k2),
                                                   k3(gbo.k3),
                                                   k4(gbo.k4),
                                                   k5(gbo.k5),
                                                   k6(gbo.k6),
                                                   k7(gbo.k7){}
GaussBlurOp& GaussBlurOp::operator=(const GaussBlurOp& gbo){
    if(this == &gbo) return *this;
    AbstractOperation<GaussBlurFunc>::operator=(gbo);
    k1 = gbo.k1;
    k2 = gbo.k2;
    k3 = gbo.k3;
    k4 = gbo.k4;
    k5 = gbo.k5;
    k6 = gbo.k6;
    k7 = gbo.k7;
    return *this;
}

bool GaussBlurOp::doOperation(const std::list<input_t>& inputs,
                              const std::list<criteria_t>& operationCriteria,
                              std::list<output_t>& outputs) throw(bad_io_mapping) {

    AbstractOperation<GaussBlurFunc>::prepareIO(inputs, operationCriteria, outputs);

    cv::Mat* in_img  = boost::any_cast<cv::Mat*>( ((*input_mapping) [k1])->getElement() );
    cv::Mat* out_img = boost::any_cast<cv::Mat*>( ((*output_mapping)[k2])->getElement() );

    size_t width  = boost::any_cast<int>( ((*criteria_mapping)[k3])->getElement() );
    size_t height = boost::any_cast<int>( ((*criteria_mapping)[k4])->getElement() );
    double sigmaX = boost::any_cast<double>( ((*criteria_mapping)[k5])->getElement() );

    // Optional parameters
    IOElement* io_sigmaY      = (*criteria_mapping)[k6];
    IOElement* io_border_type = (*criteria_mapping)[k7];
    
    double sigmaY = io_sigmaY ? boost::any_cast<double>( io_sigmaY->getElement() ) : 0;
    int border_type = io_border_type ? boost::any_cast<int>( io_border_type->getElement() ) : cv::BORDER_DEFAULT;

    (*operation)(*in_img, *out_img,   // in, out
                 width,  height,      // width, height
                 sigmaX, sigmaY,      // sigmaX, sigmaY
                 border_type);        // border_type

    return true;
}

bool GaussBlurOp::operator==(const AbstractOperation<operation_t>& op) const{
    return AbstractOperation<GaussBlurFunc>::operator==(op);
}

bool GaussBlurOp::operator!=(const AbstractOperation<operation_t>& op) const{
    return !operator==(op);
}
// -----------------------------------------------------------------------------

GaussBlurD2DNBayesOp::~GaussBlurD2DNBayesOp(){}
GaussBlurD2DNBayesOp::GaussBlurD2DNBayesOp() : GaussBlurOp() {
    k1.set_key(INPUT_NS,"model.stat.bayes"); k1.setType(TypeParseTraits<NormalBayesModel*>::name());
    input_keys->at(0).set_key(INPUT_NS,"model.stat.bayes"); input_keys->at(0).setType(TypeParseTraits<NormalBayesModel*>::name());

    k2.set_key(OUTPUT_NS,"model.stat.bayes"); k2.setType(TypeParseTraits<NormalBayesModel*>::name());
    output_keys->at(0).set_key(OUTPUT_NS,"model.stat.bayes"); output_keys->at(0).setType(TypeParseTraits<NormalBayesModel*>::name());
}
GaussBlurD2DNBayesOp::GaussBlurD2DNBayesOp(const GaussBlurD2DNBayesOp& gbo) : GaussBlurOp(gbo){}
GaussBlurD2DNBayesOp& GaussBlurD2DNBayesOp::operator=(const GaussBlurD2DNBayesOp& gbo){
    if(this == &gbo) return *this;
    GaussBlurOp::operator =(gbo);

    return *this;
}

bool GaussBlurD2DNBayesOp::doOperation(const std::list<input_t>& inputs,
                                       const std::list<criteria_t>& operationCriteria,
                                       std::list<output_t>& outputs) throw(bad_io_mapping) {

    AbstractOperation<GaussBlurFunc>::prepareIO(inputs, operationCriteria, outputs);

    NormalBayesModel* in_model = boost::any_cast<NormalBayesModel*>( ((*input_mapping)[k1])->getElement() );
    Discret2DNormalBayesModel* d2d_in_model = dynamic_cast<Discret2DNormalBayesModel*>(in_model);
    if(!d2d_in_model) throw bad_io_mapping("Discrete 2D Normal Bayes Model needed. Another Bayesian model found");

    NormalBayesModel* out_model = boost::any_cast<NormalBayesModel*>( ((*output_mapping)[k2])->getElement() );
    Discret2DNormalBayesModel* d2d_out_model = dynamic_cast<Discret2DNormalBayesModel*>(out_model);
    if(!d2d_out_model) throw bad_io_mapping("Discrete 2D Normal Bayes Model needed. Another Bayesian model found");

    size_t width  = boost::any_cast<int>( ((*criteria_mapping)[k3])->getElement() );
    size_t height = boost::any_cast<int>( ((*criteria_mapping)[k4])->getElement() );
    double sigmaX = boost::any_cast<double>( ((*criteria_mapping)[k5])->getElement() );

    // Optional parameters
    IOElement* io_sigmaY      = (*criteria_mapping)[k6];
    IOElement* io_border_type = (*criteria_mapping)[k7];

    double sigmaY = io_sigmaY ? boost::any_cast<double>( io_sigmaY->getElement() ) : 0;
    int border_type = io_border_type ? boost::any_cast<int>( io_border_type->getElement() ) : cv::BORDER_DEFAULT;

    for(unsigned int i = 0; i < d2d_in_model->get_with_evidence_data_luts().size(); i++){
        (*operation)( d2d_in_model->get_with_evidence_data_luts().at(i),
                      d2d_out_model->get_with_evidence_data_luts().at(i),
                      width,  height,      
                      sigmaX, sigmaY,      
                      border_type );
    }

    (*operation)( d2d_in_model->get_a_priori_data_lut(),
                  d2d_out_model->get_a_priori_data_lut(),
                  width,  height,
                  sigmaX, sigmaY,
                  border_type );

    return true;
}

}
